/*
 * Project: Sudoku Explainer
 * Copyright (C) 2006-2007 Nicolas Juillerat
 * 
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.data;

import java.io.Serializable;
import java.util.BitSet;
import java.util.List;
import java.util.Vector;

import com.onegravity.sudoku.solver.util.BitSetSkeleton;

/**
 * Abstract class representing a region of a sudoku grid. A region
 * is either a row, a column or a 3x3 block.
 */
public abstract class Region implements Serializable, Cloneable, Comparable<Region> {

	private static final long serialVersionUID = 4431722447466726096L;

    private Vector<Cell> cells;
    
	/**
     * Get a cell of this region by index. The order in which cells are
     * returned according to the index is not defined, but is guaranted
     * to be consistant accross multiple invocations of this method.
     * @param index the index of the cell to get, between 0 (inclusive)
     * and 9 (exclusive).
     * @return the cell at the given index
     */
    public abstract Cell getCell(int index);

    /**
     * Get the index of the given cell within this region.
     * <p>
     * The returned value is consistent with {@link #getCell(int)}.
     * @param cell the cell whose index to get
     * @return the index of the cell, or -1 if the cell does not belong to
     * this region.
     */
    public int indexOf(Cell cell) {
        for (int i = 0; i < 9; i++) {
            if (getCell(i).equals(cell)) return i;
        }
        return -1;
    }

    /**
     * Test whether this region contains the given value, that is,
     * is a cell of this region is filled with the given value.
     * @param value the value to check for
     * @return whether this region contains the given value
     */
    public boolean contains(int value) {
        for (int i = 0; i < 9; i++) {
            if (getCell(i).getValue() == value)
                return true;
        }
        return false;
    }

    /**
     * Get the potential positions of the given value within this region.
     * The bits of the returned bitset correspond to indexes of cells, as
     * in {@link #getCell(int)}. Only the indexes of cells that have the given
     * value as a potential value are included in the bitset (see
     * {@link Cell#getPotentialValues()}).
     * @param value the value whose potential positions to get
     * @return the potential positions of the given value within this region
     * @see Cell#getPotentialValues()
     */
    private BitSet getPotentialPositions(int value) {
        BitSet result = new BitSet();
        for (int index=0; index<9; index++) {
            result.set(index, getCell(index).hasPotentialValue(value));
        }
        return result;
    }

    /**
     * Does the same as the getPotentialPositions(int value) method but allows
     * re-using the BitSet to lower the memory usage.
     * @see Cell#getPotentialPositions(int value)
     */
    public void getPotentialPositions(BitSet result, int value) {
        for (int index=0; index<9; index++) {
            result.set(index, getCell(index).hasPotentialValue(value));
        }
    }

    public BitSet getPotentialPositions2(int value) {
    	BitSetSkeleton result = new BitSetSkeleton();
        result.set(0, getCell(0).hasPotentialValue(value));
        result.set(1, getCell(1).hasPotentialValue(value));
        result.set(2, getCell(2).hasPotentialValue(value));
        result.set(3, getCell(3).hasPotentialValue(value));
        result.set(4, getCell(4).hasPotentialValue(value));
        result.set(5, getCell(5).hasPotentialValue(value));
        result.set(6, getCell(6).hasPotentialValue(value));
        result.set(7, getCell(7).hasPotentialValue(value));
        result.set(8, getCell(8).hasPotentialValue(value));
        return result;
    }

    /**
     * Checks if the value has a potential positions within this region.
     * @param value the value whose potential positions to get
     * @return the first position
     * @see Cell#getPotentialPositions(int value)
     */
    public int getNrOfPotentialPositions(int value) {
    	int count = 0;
    	for (Cell cell:getCellSet()) {
    		if (cell.hasPotentialValue(value)) count++;
    	}
        return count++;
    }

    /**
     * Checks if the value has a potential positions within this region.
     * @param value the value whose potential positions to get
     * @return the first position
     * @see Cell#getPotentialPositions(int value)
     */
    public int getPosOfPotentialPositions(int value) {
    	int index = 0;
    	for (Cell cell:getCellSet()) {
    		if (cell.hasPotentialValue(value)) return index;
    		index++;
    	}
        return -1;
    }

    public BitSet copyPotentialPositions(int value) {
        return getPotentialPositions(value); // No need to clone, this is alreay hand-made
    }

    /**
     * Must be called at the end of the subclasses' constructors
     * to precompute some region data structures
     */
    protected void initRegion() {
        cells = new Vector<Cell>();
        for (int i = 0; i < 9; i++) {
            cells.add(getCell(i));
        }
    }
    
    /**
     * Get the cells of this region. The iteration order of the result
     * matches the order of the cells returned by {@link #getCell(int)}.
     * @return the cells of this region.
     */
    public List<Cell> getCellSet() {
        return cells;
    }

    @SuppressWarnings("unchecked")
	public List<Cell> getCellSetClone() {
        return (List<Cell>) cells.clone();
    }

    /**
     * Return the cells that are common to this region and the
     * given region
     * @param other the other region
     * @return the cells belonging to this region and to the other region
     */
	public List<Cell> commonCells(Region other) {
    	@SuppressWarnings("unchecked")
		Vector<Cell> result = (Vector<Cell>) ((Vector<Cell>) getCellSet()).clone();
        result.retainAll(other.getCellSet());
        return result;
    }

    /**
     * Test whether this region crosses an other region.
     * <p>
     * A region crosses another region if they have at least one
     * common cell. In particular, any rows cross any columns.
     * @param other the other region
     * @return whether this region crosses the other region.
     */
    public boolean crosses(Region other) {
        return !commonCells(other).isEmpty();
    }

    /**
     * Get the number of cells of this region that are still empty.
     * @return the number of cells of this region that are still empty
     */
    public int getEmptyCellCount() {
        int result = 0;
        for (int i = 0; i < 9; i++) {
            if (getCell(i).isEmpty()) result++;
        }
        return result;
    }

    /**
     * Get a string representation of this region's type
     */
    @Override
    public abstract String toString();

    /**
     * Get a string representation of this region
     * @return a string representation of this region
     */
    public abstract String toFullString();

}